package com.swak.asm.compiler;

import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.swak.utils.ClassHelper;

import javassist.CtClass;

public class JavassistCompiler {

	private static final Pattern PACKAGE_PATTERN = Pattern.compile("package\\s+([$_a-zA-Z][$_a-zA-Z0-9\\.]*);");
	private static final Pattern CLASS_PATTERN = Pattern.compile("class\\s+([$_a-zA-Z][$_a-zA-Z0-9]*)\\s+");
	private static final Pattern IMPORT_PATTERN = Pattern.compile("import\\s+([\\w\\.\\*]+);\n");
	private static final Pattern EXTENDS_PATTERN = Pattern.compile("\\s+extends\\s+([\\w\\.]+)[^\\{]*\\{\n");
	private static final Pattern IMPLEMENTS_PATTERN = Pattern.compile("\\s+implements\\s+([\\w\\.]+)\\s*\\{\n");
	private static final Pattern METHODS_PATTERN = Pattern.compile("\n(private|public|protected)\\s+");
	private static final Pattern FIELD_PATTERN = Pattern.compile("[^\n]+=[^\n]+;");

	public Class<?> compile(String code) {
		code = code.trim();
		Matcher matcher = PACKAGE_PATTERN.matcher(code);
		String pkg;
		if (matcher.find()) {
			pkg = matcher.group(1);
		} else {
			pkg = "";
		}
		matcher = CLASS_PATTERN.matcher(code);
		String cls;
		if (matcher.find()) {
			cls = matcher.group(1);
		} else {
			throw new IllegalArgumentException("No such class name in " + code);
		}
		String className = pkg != null && pkg.length() > 0 ? pkg + "." + cls : cls;
		try {
			return Class.forName(className, true, ClassHelper.getCallerClassLoader(getClass()));
		} catch (ClassNotFoundException e) {
			if (!code.endsWith("}")) {
				throw new IllegalStateException("The java code not endsWith \"}\", code: \n" + code + "\n");
			}
			try {
				return doCompile(className, code);
			} catch (RuntimeException t) {
				throw t;
			} catch (Throwable t) {
				throw new IllegalStateException("Failed to compile class, cause: " + t.getMessage() + ", class: "
						+ className + ", code: \n" + code + "\n, stack: " + ClassHelper.toString(t));
			}
		}
	}

	public Class<?> doCompile(String name, String source) throws Throwable {
		CtClassBuilder builder = new CtClassBuilder();
		builder.setClassName(name);

		// process imported classes
		Matcher matcher = IMPORT_PATTERN.matcher(source);
		while (matcher.find()) {
			builder.addImports(matcher.group(1).trim());
		}

		// process extended super class
		matcher = EXTENDS_PATTERN.matcher(source);
		if (matcher.find()) {
			builder.setSuperClassName(matcher.group(1).trim());
		}

		// process implemented interfaces
		matcher = IMPLEMENTS_PATTERN.matcher(source);
		if (matcher.find()) {
			String[] ifaces = matcher.group(1).trim().split("\\,");
			Arrays.stream(ifaces).forEach(i -> builder.addInterface(i.trim()));
		}

		// process constructors, fields, methods
		String body = source.substring(source.indexOf('{') + 1, source.length() - 1);
		String[] methods = METHODS_PATTERN.split(body);
		String className = ClassHelper.getSimpleClassName(name);
		Arrays.stream(methods).map(String::trim).filter(m -> !m.isEmpty()).forEach(method -> {
			if (method.startsWith(className)) {
				builder.addConstructor("public " + method);
			} else if (FIELD_PATTERN.matcher(method).matches()) {
				builder.addField("private " + method);
			} else {
				builder.addMethod("public " + method);
			}
		});

		// compile
		ClassLoader classLoader = ClassHelper.getCallerClassLoader(getClass());
		CtClass cls = builder.build(classLoader);
		return cls.toClass(classLoader, JavassistCompiler.class.getProtectionDomain());
	}
}
